package data;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Vector;

import data.Status.StatusEnum;

/**
 * Data structure to hold Show data.
 * @author Quinn
 *
 */
public class Show implements Serializable
{
	/**************************************************************************
	 * DISCUSS - standards
	 * NOTE: We need to come up with some kind of standard so that example    *
	 * description always holds                                               *
	 * "Show Name: GoT; Episode Title: N/A; Season: 1; Episode: 7" etc.       *
	 *************************************************************************/
	
	/**
	 * The serial number for Show.
	 */
	private static final long serialVersionUID = 01;

	/**
	 * name of the show.
	 */
	private String showName;

	/** list of the seasons in the show */
	private Vector<Season> seasons = new Vector<Season>();
	
	private Season selectedSeason;
	
	/** enum for show status (incomplete) */
	//public enum Status { CHECK, HOLD, PAUSE, HIATUS }
	
	/** The show's status */
	private StatusEnum status = StatusEnum.CHECK;
	
	/**
	 * Constructor. DISCUSS - blank constructor?
	 * @quinn: 	we need to plan how construction will happen of the show->season->episode
	 * 			additionally, our current model doesn't support 'goToNextEpisode()' very well, does it?
	 */
	public Show(String name)
	{
		this.showName = name;
	}
	
	/**
	 * Returns a string representation of the show.
	 * @return String representation of the show.
	 */
	public String toString()
	{
		String returnString = this.getShowName()+"\n";
		for(Season s : seasons)
		{
			returnString += s.toString()+"\n";
		}
		return returnString;
	}
	
	/**
	 * Returns a string representation of the show.
	 * @return String representation of the show.
	 */
	public String toXML()
	{
		String tabs = "";
		String returnXML = tabs+"<show>\n";
		tabs += "\t";
		returnXML += tabs+"<showName>"+this.getShowName()+"</showName>\n";
		returnXML += tabs+"<selectedSeason>"+this.getSelectedSeason().getSeasonNumber()+"</selectedSeason>\n";
		returnXML += tabs+"<seasons>\n";
		for(Season s : seasons)
		{
			returnXML += s.toXML(tabs+"\t");
		}
		returnXML += tabs+"</seasons>\n";
		tabs = tabs.replaceFirst("\t", "");

		returnXML += tabs+"</show>\n";
		return returnXML;
	}
	
	/**
	 * Returns a string representation of the show.
	 * @return String representation of the show.
	 */
	public String toXML(String tabs)
	{
		String returnXML = tabs+"<show>\n";
		tabs += "\t";
		returnXML += tabs+"<showName>"+this.getShowName()+"</showName>\n";
		if(this.getSelectedSeason() != null)
		{
			returnXML += tabs+"<selectedSeason>"+this.getSelectedSeason().getSeasonNumber()+"</selectedSeason>\n";
		}
		returnXML += tabs+"<seasons>\n";
		for(Season s : seasons)
		{
			returnXML += s.toXML(tabs+"\t");
		}
		returnXML += tabs+"</seasons>\n";
		tabs = tabs.replaceFirst("\t", "");

		returnXML += tabs+"</show>\n";
		return returnXML;
	}

	/**
	 * Returns the seasons.
	 * @return - all the seasons for the show.
	 */
	public Vector<Season> getSeasons()
	{
		return seasons;
	}
	
	/**
	 * Return's the season at a given number. 
	 * @param seasonNum the season number to look for.
	 * @return A season given the season number. 
	 */
	public Season getSeason(int seasonNum)
	{
		for(Season s : this.seasons)
		{
			if(s.getSeasonNumber() == seasonNum)
			{
				return s;
			}
		}
		return null;
	}
	
	/**
	 * setSelectedSeason - 
	 * 
	 * will set the selected season using the season number given and give a boolean
	 * return value which indicates success/failure.  Boolean return type may not be necessary...
	 * 
	 * @param seasonNumber - the season number
	 * @return - the success/failure of the operation
	 */
	public boolean setSelectedSeason(int seasonNumber)
	{
		boolean wasSet = false;
		LinkedList<Season> seasonsToUnset = new LinkedList<Season>();
		for(Season s : seasons)
		{
			if(s.getSeasonNumber() == seasonNumber)
			{
				selectedSeason = s;
				wasSet = true;
			}
			else
			{
				seasonsToUnset.add(s);
			}
		}
		if(wasSet)
		{
			for(Season s: seasonsToUnset)
			{
				s.setSelectedEpisode(-1);
			}
		}
		seasonsToUnset = null;
		return wasSet;
	}
	/**
	 * setSelectedEpisode - set the currently selected episode using the season number and episode number.  
	 * @param seasonNumber - the season number (not index)
	 * @param episodeNumber
	 * @return
	 */	
	public boolean setSelectedEpisode(int seasonNumber, int episodeNumber)
	{
		setSelectedSeason(seasonNumber);
		
		selectedSeason.setSelectedEpisode(episodeNumber);
		
		return true;
	}

	/**
	 * Returns the selected season.  If it's not set it will be set, and will set the selected episode
	 * If there's nothing to set it to (empty) then it returns null.
	 * @return - the selected season
	 */
	public Season getSelectedSeason()
	{
	
		return selectedSeason;
		
	}
	
	/** 
	 * Sets the selected episode for the season based on the inputs
	 * 
	 * @param latestSeason - 	If true, finds the latest season with torrents
	 * 							If false, finds the oldest season with torrents
	 * @param latestEpisode - 	If true, finds the latest episode in the season with torrents
	 * 							If false, finds the oldest episode in the season with torrents
	 * @return 
	 * 
	 */
	public boolean setPreferedSelectedEpisode(boolean latestSeason, boolean latestEpisode)
	{
		boolean returnBool = false;
		ListIterator<Season> sIterator;
		if(latestSeason)
		{
			sIterator = seasons.listIterator(seasons.size());
			while(sIterator.hasPrevious())
			{
				Season s = sIterator.previous();	
				if(s.setPreferedSelectedEpisode(latestEpisode))
				{
					this.setSelectedSeason(s.getSeasonNumber());
					returnBool = true;
					break;
				}
			}			
		}
		else
		{
			sIterator = seasons.listIterator();
			while(sIterator.hasNext())
			{
				Season s = sIterator.next();	
				if(s.setPreferedSelectedEpisode(latestEpisode))
				{
					this.setSelectedSeason(s.getSeasonNumber());
					returnBool = true;
					break;
				}
			}
		}
		sIterator = null;
		return returnBool;
	}

	/**
	 * get's the name of the show.
	 * @return String title
	 */
	public String getShowName() 
	{
		return showName;
	}
	
	/**
	 * getStatus - Return the show's current status
	 * @return the show's status
	 */
	public StatusEnum getStatus()
	{
		return status;
	}
	
	/**
	 * setStatus - set the show's current status
	 * @param s - the Status to set the show to
	 */
	public void setStatus(StatusEnum s)
	{
		status = s;
	}
	
	/**
	 * goToNextEpisode - Go to the next available episode (if there is one).  If there is not a next episode then... what?
	 * Set status to pause?
	 */
	public boolean goToNextEpisode()
	{
		boolean toReturn = false;
		
		if(this.getSelectedSeason().goToNextEpisode())
		{
			//found the next ep
			toReturn = true;
		}
		else if(!this.setSelectedSeason(this.getSelectedSeason().getSeasonNumber()+1))
		{
				//There's no next season... pause the show
				status = StatusEnum.PAUSE;
		}
		else
		{
				//manually reset to episode 1 (in case they were in this season before... 
				this.setSelectedEpisode(this.getSelectedSeason().getSeasonNumber(), 1);
				toReturn = true;
		}
		
		return toReturn;
		
	}
	
	/**
	 * 
	 * @return the episode that should next be downloaded
	 */
	public Episode getEpisodeToDownload()
	{
		return selectedSeason.getSelectedEpisode();//epToDownload;
	}
	
	public boolean merge(Show newShow)
	{
		boolean returnBool = false;
		Vector<Season> newSeasonList = newShow.getSeasons();
		Vector<Season> toAddSeasonList = new Vector<Season>();
		if(seasons.size() < newSeasonList.size())
		{
			for(Season newSeason:newSeasonList)
			{
				boolean foundSeasonNumber = false;
				for(Season oldSeason:seasons)
				{
					if(oldSeason.getSeasonNumber() == newSeason.getSeasonNumber())
					{
						foundSeasonNumber = true;
						break;
					}
				}
				if(!foundSeasonNumber)
				{
					toAddSeasonList.add(newSeason);
				}
			}
		}
		for(Season oldSeason: seasons)
		{
			for(Season newSeason: newSeasonList)
			{
				if(oldSeason.getSeasonNumber() == newSeason.getSeasonNumber())
				{
					if(oldSeason.merge(newSeason))
					{
						returnBool = true;
					}
				}
			}
		}
		for(Season toAddSeason : toAddSeasonList)
		{
			for(Episode toAdd : toAddSeason.getEpisodes())
			{
				if(toAdd.getEpisodeNumber() > 0)
				{
					this.addEpisode(toAdd, toAddSeason.getSeasonNumber());
					returnBool = true;
				}
			}
		}
		return returnBool;
	}

	/**
	 * Add's a episode to show.
	 * @param ep Episode to add
	 * @param seasonNum Integer representation of the season to add episode to.
	 */
	public void addEpisode(Episode ep, int seasonNum) 
	{
		//Do some initial checks before we add
		if(!ep.isValid())
		{
			return;
		}
		if(seasons.isEmpty())
		{
			//Add the season, easy!
			Season newSeason = new Season(seasonNum);
			newSeason.addEpisode(ep);
			seasons.add(newSeason);
		}
		else if(seasons.lastElement().getSeasonNumber()<seasonNum)
		{
			//Our last season is less then the new season
			Season newSeason = new Season(seasonNum);
			newSeason.addEpisode(ep);
			seasons.add(newSeason);
		}
		else
		{
			for(int i=0; i<seasons.size(); i++)
			{
				Season s = seasons.get(i);
				if(s.getSeasonNumber() == seasonNum)
				{
					s.addEpisode(ep);
					break;
				}
				//Check to see if the season needs to be added. 
				else if(s.getSeasonNumber()> seasonNum)
				{
					
					Season newSeason = new Season(seasonNum);
					//Since this is the only episode in season, add it now.
					newSeason.addEpisode(ep);
					//Reached the end of the list. 
					//Check to see if season belongs at the end.
					//Note: If s is the only season, then we have to add it before.
					if(seasons.firstElement().equals(s))
					{
						seasons.insertElementAt(newSeason,0);
					}
					else
					{
						seasons.add(i-1, newSeason);
					}
					break;
				}
			}
		}
		this.setSelectedEpisode(seasonNum, ep.getEpisodeNumber());

	}
}

